package org.cainiao.portal.config.httpclient;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.*;
import reactor.core.publisher.Mono;

import java.util.regex.Pattern;

/**
 * <br />
 * <p>
 * Author: Cai Niao(wdhlzd@163.com)<br />
 */
@Configuration
public class HttpClientConfig {

    /**
     * 当作为 OAuth2 客户端调用授权服务器 API 时<br />
     * 需要在发请求前在 HTTP Header 上设置 access token
     *
     * @param oAuth2AuthorizedClientManager OAuth2AuthorizedClientManager
     * @return WebClient
     */
    @Bean
    WebClient webClient(OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) {
        return WebClient.builder()
            .exchangeStrategies(ExchangeStrategies.builder()
                // 缓冲区 16 MB，否则绘图图片会超过大小
                .codecs(codecs -> codecs.defaultCodecs().maxInMemorySize(16 * 1024 * 1024))
                .build())
            .filter(new DynamicExchangeFilterFunction(oAuth2AuthorizedClientManager)).build();
    }

    @RequiredArgsConstructor
    public static class DynamicExchangeFilterFunction implements ExchangeFilterFunction {

        private static final Pattern larkUrlPattern = Pattern.compile("^https://auth\\.milin\\.website/tenant/.*");
        private final OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager;

        @Override
        @NonNull
        public Mono<ClientResponse> filter(@NonNull ClientRequest request, @NonNull ExchangeFunction exchangeFunction) {
            String url = request.url().toString();
            /*
             * larkAccessToken 为空时，不要加一个空字符串的 Bearer Header
             * 否则会导致资源服务器的 BearerTokenAuthenticationFilter 抛异常
             * 导致资源服务器无法实现允许匿名访问的端点
             * 资源服务器希望如果没有 access token 时 BearerTokenAuthenticationFilter 不抛异常，而是放过
             * 由后边的 AuthorizationFilter 根据策略决定是否通过，这样才能支持匿名访问
             */
            String larkAccessToken;
            if (needLarkAccessToken(url) && StringUtils.hasText(larkAccessToken = getLarkAccessToken())) {
                return exchangeFunction.exchange(ClientRequest.from(request)
                    .headers(headers -> headers.setBearerAuth(larkAccessToken))
                    .build());
            }
            return exchangeFunction.exchange(request);
        }

        private boolean needLarkAccessToken(String url) {
            return larkUrlPattern.matcher(url).matches();
        }

        private String getLarkAccessToken() {
            try {
                OAuth2AuthorizedClient authorizedClient = oAuth2AuthorizedClientManager.authorize(OAuth2AuthorizeRequest
                    .withClientRegistrationId("cn-auth-center-portal-client")
                    .principal(SecurityContextHolder.getContext().getAuthentication())
                    .build());
                return authorizedClient == null ? "" : authorizedClient.getAccessToken().getTokenValue();
            } catch (Exception e) {
                e.printStackTrace();
                return "";
            }
        }
    }
}
